# coding=utf8

from pyjvm.classfile.parser import ClassFileParser
from pyjvm.classpath import Classpath
from pyjvm.runtime.heap import Heap
from pyjvm.runtime.metadata import Class, Field, AccessFlags
from pyjvm.runtime.strpool import StringPool
from pyjvm.utils import java_default_of, ClassNameUtil


def parse_class_file(class_bytes: bytes) -> Class:
    class_file = ClassFileParser(class_bytes).parse()
    return Class.new_class(class_file)


def resolve_super_class(klass: Class):
    if klass.super_class_name is not None:
        # 只有java.lang.Object 的父类为None
        klass.super_class = klass.loader.load_class(
            klass.super_class_name)


def resolve_interfaces(klass: Class):
    for i, interface_name in enumerate(klass.interface_names):
        klass.interfaces[i] = klass.loader.load_class(interface_name)


def get_field_const_value(field: Field):
    const_val = field.const_val
    if const_val is not None:
        if field.descriptor in 'ZBCSIJFD':
            return field.const_val
        elif field.descriptor == 'Ljava/lang/String;':
            py_str = field.const_val
            return StringPool.new(field.klass.loader, py_str)
    return java_default_of(field.descriptor)


def init_final_static_fields(klass: Class):
    for field in klass.fields:
        if field.is_static:
            field_id = id(field)
            if field.is_final:
                klass.static_vars[field_id] = get_field_const_value(field)
            else:
                klass.static_vars[field_id] = java_default_of(field.descriptor)


#
# def verify(klass: Class):
#     pass


def prepare(klass: Class):
    init_final_static_fields(klass)


def link(klass: Class):
    # verify(klass)
    prepare(klass)


PRIMITIVE_CLASS = {
    "void": "V",
    "boolean": "Z",
    "byte": "B",
    "short": "S",
    "int": "I",
    "long": "J",
    "char": "C",
    "float": "F",
    "double": "D",
}

J_CLASS_NAME = 'java/lang/Class'


class LoaderClassError(Exception):
    pass


class ClassLoader(object):

    def __init__(self, cp: Classpath):
        self.__loaded_classes = {}
        self.__loading_classes = set()
        self.__cp = cp
        self.__load_basic_classes()
        self.__load_primitive_classes()
        for klass in self.__loaded_classes.values():
            self.__set_j_class(klass)

    def __load_primitive_class(self, class_name: str):
        klass = Class()
        klass.is_public = True
        klass.access_flags = AccessFlags.ACC_PUBLIC
        klass.name = class_name
        klass.loader = self
        klass.inited = True
        klass.instance_slot_count = 0
        klass.is_primitive = True
        self.__loaded_classes[class_name] = klass

    def __set_j_class(self, klass: Class):
        j_class_class = self.__loaded_classes[J_CLASS_NAME]
        klass.j_class = Heap.new_object(j_class_class)
        klass.j_class.extra = klass

    def __load_primitive_classes(self):
        for name in PRIMITIVE_CLASS:
            self.__load_primitive_class(name)

    def __load_basic_classes(self):
        self.load_class(J_CLASS_NAME)

    def load_class(self, class_name: str) -> Class:
        while class_name in self.__loading_classes:
            pass
        if class_name in self.__loaded_classes:
            return self.__loaded_classes[class_name]

        self.__loading_classes.add(class_name)
        try:
            if class_name.startswith('['):
                klass = self.__load_array_class(class_name)
            else:
                klass = self.__load_non_array_class(class_name)

            if J_CLASS_NAME in self.__loaded_classes:
                self.__set_j_class(klass)
            self.__loaded_classes[class_name] = klass
            return klass
        except Exception as _:
            raise LoaderClassError('无法加载类:%s' % class_name)
        finally:
            self.__loading_classes.remove(class_name)

    def __load_non_array_class(self, class_name: str) -> Class:
        class_bytes = self.__cp.read_class_bytes(class_name)
        klass = self.__define_class(class_bytes)
        link(klass)
        return klass

    def __define_class(self, class_bytes: bytes) -> Class:
        klass = parse_class_file(class_bytes)
        klass.loader = self
        resolve_super_class(klass)
        resolve_interfaces(klass)
        return klass

    def __load_array_class(self, class_name: str) -> Class:
        klass = Class()
        klass.access_flags = AccessFlags.ACC_PUBLIC
        klass.is_public = True
        klass.name = class_name
        klass.loader = self
        klass.super_class = self.load_class('java/lang/Object')
        klass.interfaces = list(
            map(self.load_class, ['java/lang/Cloneable', 'java/io/Serializable']))
        klass.is_array_class = True
        return klass

    def get_component_array_class(self, klass: Class) -> Class:
        class_name = ClassNameUtil.get_array_class_name(klass.name)
        return self.load_class(class_name)

    def get_component_class(self, array_class: Class) -> Class:
        class_name = ClassNameUtil.get_component_class_name(array_class.name)
        return self.load_class(class_name)


__all__ = ['ClassLoader']
